package com.trg.flex.remote
{
	import com.sample.Defaults;
	import com.trg.flex.FlexUtils;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	import mx.messaging.messages.IMessage;
	import mx.rpc.IResponder;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	
	public class RemoteCall extends EventDispatcher
	{
		/**
		 * No ordering is guaranteed.
		 */
		public static const UNORDERED:String = "unordered";
		
		/**
		 * Request will not be sent until all previous requests have completed, and
		 * no subsequent request will be sent until this one has completed.
		 */
		public static const ORDERED:String = "ordered";
		
		/**
		 * "Last call wins"
		 * 
		 * The last call with OVERWRITE strategy will be the last one to reach the server.
		 * Prior calls are not guaranteed to reach the server at all, but if they do
		 * they will complete before the last call.
		 * 
		 * OVERWRITE calls only exhibit this behavior with other OVERWRITE calls. In
		 * relation to calls with other strategies, they have the same behavior as UNORDERD.
		 */
		public static const OVERWRITE:String = "overwrite";
		
		/**
		 * Requests may be sent in any order, however this call will not complete until
		 * all prior calls have completed and no subsequent call will complete until this
		 * call has completed.
		 */
		public static const RETURN_ORDERED:String = "returnOrdered";
		
		/**
		 * No queueing or connection management is enforced. The call is processed
		 * directly. Any connection or authentication faults return as regular faults.
		 */
		public static const BYPASS:String = "bypass";
		
		public static const STATUS_NEW:String = "new";
		public static const STATUS_PENDING:String = "pending";
		public static const STATUS_RESULT:String = "result";
		public static const STATUS_FAULT:String = "fault";
		
		protected static const OUTCOME_RESULT:String = "result";
		protected static const OUTCOME_FAULT:String = "fault";
		protected static const OUTCOME_AUTHENTICATION_FAULT:String = "authenticationFault";
		protected static const OUTCOME_CONNECTION_FAULT:String = "connectionFault";
		
		public function RemoteCall() {
			super();
			authenticationRequired = Defaults.getInstance().RemoteCall_authenticationRequired;
			showStatus = Defaults.getInstance().RemoteCall_showStatus;
		}
		
		
		private var _queue:String = "";
		public function get queue():String {
			return _queue;
		}
		public function set queue(value:String):void {
			if (_lock) {
				throw new Error("Setting queue on a RemoteCall after it has been called is not allowed.");
			} else {
				_queue = value;
			}
		}
		
		private var _strategy:String = UNORDERED;
		public function get strategy():String {
			return _strategy;
		}
		public function set strategy(value:String):void {
			if (_lock) {
				throw new Error("Setting strategy on a RemoteCall after it has been called is not allowed.");
			} else if (value != ORDERED && value != OVERWRITE && value != RETURN_ORDERED && value != BYPASS && value != UNORDERED) {
				throw new Error("An invalid value was specified for RemoteCall.strategy.");
			} else {
				_strategy = value;
			}
		}
		
		private var _connection:String;
		public function get connection():String {
			return _connection;
		}
		public function set connection(value:String):void {
			if (_lock) {
				throw new Error("Setting connection on a RemoteCall after it has been called is not allowed.");
			} else {
				_connection = value;
			}
		}
		
		private var _systemModal:Boolean = false;
		public function get systemModal():Boolean {
			return _systemModal;
		}
		public function set systemModal(value:Boolean):void {
			if (_lock) {
				throw new Error("Setting systemModal on a RemoteCall after it has been called is not allowed.");
			} else {
				_systemModal = value;
			}
		}
		
		public var authenticationRequired:Boolean; //TODO
		
		public var description:String;
		
		public var longDescription:String;
		
		public var resultMessage:String;
		
		public var faultMessage:String;
		
		public var showStatus:Boolean;
		
		
		public var result:*;
		
		public var fault:Error;
		
		private var _status:String = STATUS_NEW;
		[Bindable("statusChanged")]
		public function get status():String {
			return _status
		}
		protected function setStatus(value:String):void {
			if (value != STATUS_PENDING && value != STATUS_RESULT && value != STATUS_FAULT && value != STATUS_NEW) {
				throw new Error("An invalid value was specified for RemoteCall.status.");
			}
			_status = value;
			dispatchEvent(new Event("statusChanged"));
		}
		
		[Bindable("statusChanged")]
		public function get complete():Boolean {
			return _status == STATUS_RESULT || _status == STATUS_FAULT;
		}
		
		[Bindable("statusChanged")]
		public function get pending():Boolean {
			return _status == STATUS_PENDING;
		}
		
		[Bindable("statusChanged")]
		public function get faulted():Boolean {
			return _status == STATUS_FAULT;
		}
		
		
		public function addResultCallback(func:Function, data:Object = null, thisArg:Object = null):RemoteCall {
			if (thisArg == null && data == null) {
				resultCallbacks.unshift(func);
			} else {
				resultCallbacks.unshift({"func": func, "thisArg": thisArg, "data": data});
			}
			return this;
		}
		
		public function addFaultCallback(func:Function, data:Object = null, thisArg:Object = null):RemoteCall {
			if (thisArg == null && data == null) {
				faultCallbacks.unshift(func);
			} else {
				faultCallbacks.unshift({"func": func, "thisArg": thisArg, "data": data});
			}
			return this;
		}
		
		public function addCallback(func:Function, data:Object = null, thisArg:Object = null):RemoteCall {
			addResultCallback(func, data, thisArg);
			addFaultCallback(func, data, thisArg);
			return this;
		}
		
		public function call(strategy:String = null, queue:String = null):RemoteCall {
			RemoteCallManager.instance().call(this, strategy, queue);
			return this;
		}
		
		public var responders:Array;
		
		public function addResponder(responder:IResponder):RemoteCall {
			if (responder) {
				if (responders == null)
					responders = [];
				responders.push(responder);
			}
			return this;
		}
		
		
		private var resultCallbacks:Array = [];
		private var faultCallbacks:Array = [];
		private var _lock:Boolean = false;
		
		
		public function lock():void {
			_lock = true
			setStatus(STATUS_PENDING);
		}
		
		/**
		 * the callback function will be of the form
		 * function (call:RemoteCall, outcome:String):void
		 * 
		 * call - this
		 * outcome - one of "result", "fault", "connectionFault", "authenticationFault"
		 */
		public function fire(callback:Function):void {
			throw new Error("Abstract method 'fire()' must be implemented.");
		}
		
		public function applyOutcome():void {
			throw new Error("Abstract method 'applyOutcome()' must be implemented.");
		}
		

		
		protected function applyResult(event:ResultEvent):void {
			result = event.result;
			setStatus(STATUS_RESULT);
			for each (var callback:* in resultCallbacks) {
				if (callback == null) {
					
				} else if (callback is Function) {
					FlexUtils.callFunc(callback, null, this);
				} else {
					FlexUtils.callFunc(callback.func, callback.thisArg, this, callback.data)
				}
			}
			if (responders && responders.length > 0) {
				for each (var responder:IResponder in responders) {
					if (responder)
						responder.result(event);
				}
			}
			dispatchEvent(event);
		}
		
		protected function applyFault(event:FaultEvent):void {
			fault = event.fault;
			setStatus(STATUS_FAULT);
			for each (var callback:* in faultCallbacks) {
				if (callback == null) {
					
				} else if (callback is Function) {
					FlexUtils.callFunc(callback, null, this);
				} else {
					FlexUtils.callFunc(callback.func, callback.thisArg, this, callback.data)
				}
			}
			if (responders && responders.length > 0) {
				for each (var responder:IResponder in responders) {
					if (responder)
						responder.fault(event);
				}
			}
			dispatchEvent(event);
		}
		
	}
}